package com.zuorenke.vertx;

import com.zuorenke.exception.ExceptionPerformer;
import com.zuorenke.help.ReflectionUtils;
import com.zuorenke.model.Performer;
import com.zuorenke.vertx.anno.Router;
import com.zuorenke.vertx.anno.Route;
import com.zuorenke.vertx.factory.BeanFactory;
import com.zuorenke.vertx.config.VertxProperties;
import io.vertx.core.http.HttpMethod;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @author Z.R.K
 * @description
 * @create 2019-05-17 11:28:15
 **/
@Component
@Slf4j
public class WebRouteMapping implements InitializingBean {
	
	@Resource
	private VertxProperties vertxProperties;
	
	@Resource
	private ExceptionPerformer exceptionPerformer;
	
	private Map<String, Map<HttpMethod, Performer>> servicesMapping = new HashMap<>();
	
	public Performer get(String path, HttpMethod method){
		if(path.endsWith("/")){
			path = path.substring(0, path.length() - 1);
		}
		path = path.toLowerCase();
		Map<HttpMethod, Performer> performers = servicesMapping.get(path);
		Performer performer;
		if(performers == null){
			performer = exceptionPerformer.notFoundPerformer;
		}else{
			performer = performers.get(method);
			if(performer == null){
				performer = exceptionPerformer.methodNotSupportPerformer;
			}
		}
		return performer;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		// 需要扫描注册的Router路径
		Reflections reflections = ReflectionUtils.getReflections(vertxProperties.getWebApiPackages());
		Set<Class<?>> routers = reflections.getTypesAnnotatedWith(Router.class);
		for(Class clazz : routers){
			Method[] methods = clazz.getMethods();
			Router router = (Router) clazz.getAnnotation(Router.class);
			String routerPath = router.value();
			for(Method method : methods){
				if(method.isAnnotationPresent(Route.class)){
					Route route = method.getAnnotation(Route.class);
					String routePath = route.value();
					String fullPath = mergePath(routerPath, routePath);
					Performer performer = initPerformer(clazz, method, route);
					
					Map<HttpMethod, Performer> mapping = servicesMapping.computeIfAbsent(fullPath,
							x -> new EnumMap<>(HttpMethod.class));
					log.info("init performer {}",performer);
					if(mapping.get(route.method()) != null){
						log.warn("routeMapping {} is duplicate!", fullPath);
					}
					mapping.put(route.method(), performer);
				}
			}
		}
	}
	
	private String mergePath(String routerPath, String routePath) {
		String fullPath = "";
		if(!routerPath.startsWith("/")){
			fullPath = "/";
		}
		fullPath += routerPath;
		if(!routePath.startsWith("/") && !fullPath.endsWith("/")){
			fullPath += "/";
		}
		fullPath += routePath;
		
		if(fullPath.endsWith("/")){
			fullPath = fullPath.substring(0, fullPath.length()-1);
		}
		return fullPath.toLowerCase();
	}
	
	private Performer initPerformer(Class clazz, Method method, Route route) {
		Performer performer = new Performer();
		performer.setBean(BeanFactory.builder(clazz));
		performer.setMethod(method);
		performer.setHttpMethod(route.method());
		Parameter[] parameters = method.getParameters();
		List<String> paramNames = new ArrayList<>(parameters.length);
		List<Class> paramClasses = new ArrayList<>(parameters.length);
		List<Type> paramTypes = new ArrayList<>();
		List<Annotation[]> paramAnnos = new ArrayList<>(parameters.length);
		for(Parameter param : parameters){
			paramNames.add(param.getName());
			paramClasses.add(param.getType());
			paramTypes.add(param.getParameterizedType());
			Annotation[] annos = param.getAnnotations();
			if(annos == null){
				annos = new Annotation[0];
			}
			paramAnnos.add(annos);
		}
		performer.setParamNames(paramNames);
		performer.setParamClasses(paramClasses);
		performer.setParamTypes(paramTypes);
		performer.setParamAnnos(paramAnnos);
		return performer;
	}
}
